/**
 * 
 */
package it.polimi.cg16.model;

import it.polimi.cg16.model.Player.NoMoneyException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Andrea & Michele
 * 
 */
public class GameBoard {
    // Constants
    static final int NUMBER_OF_REGION = 18;
    static final int NUMBER_OF_CELL = 42;
    static final int SHEEPSBURG = 0;
    static final int CARD_PER_TERRAIN = 5;
    static final int STARTER_CARD_FOREACH = 1;
    static final int MOVE_SHEPHERD_FAR = 1;
    static final int MAX_FENCES = 20;
    static final int MAX_MONEY_2_PLAYERS = 30;
    static final int MAX_MONEY_MANY_PLAYERS = 20;
    private static final Logger LOGGER = Logger.getGlobal();
    private static final String CONTEXT = "context";

    private Dice dice;
    private DeckFactory deckFactory;
    private SheepFactory sheepFactory;

    private List<Card> deck;

    private Region[] region;
    private Cell[] cell;
    private Shepherd[] shepherd;
    private BlackSheep blackSheep;
    private Wolf wolf;
    private Player[] player;
    private int fences;

    public GameBoard(int numberOfPlayer) {
        dice = new Dice();
        // Initialization of decks
        deckFactory = new DeckFactory();
        deck = deckFactory.deckCreator(CARD_PER_TERRAIN);

        sheepFactory = new SheepFactory();

        // Initialization of region
        RegionFactory regionFactory = new RegionFactory();
        region = regionFactory.regionCreator();
        // Initialization of cell
        CellFactory cellFactory = new CellFactory();
        cell = cellFactory.cellCreator(region);

        // Initialization of Black sheep
        blackSheep = new BlackSheep(0, region[SHEEPSBURG]);
        region[0].addSheep(blackSheep);

        // Initialization of Wolf
        wolf = new Wolf(region[SHEEPSBURG]);

        // Initialization of Players
        player = new Player[numberOfPlayer];
        for (int i = 0; i < numberOfPlayer; i++) {
            player[i] = new Player();
        }
        // Initialization of Shepherds
        if (numberOfPlayer < 3) {
            shepherd = new Shepherd[numberOfPlayer * 2];
            giveMoney(MAX_MONEY_2_PLAYERS);
        } else {
            shepherd = new Shepherd[numberOfPlayer];
            giveMoney(MAX_MONEY_MANY_PLAYERS);
        }
        for (int i = 0; i < shepherd.length; i++) {
            shepherd[i] = new Shepherd(Color.values()[i]);
            player[i % numberOfPlayer].addShepherd(shepherd[i]);
        }
        // Initialization of fences
        fences = MAX_FENCES;
    }

    /**
     * Buy a terrain card
     * 
     * @param playerNumber
     * @param terrain
     * @return true if shop is done, false otherwise
     */
    public boolean buyCard(Player actualPlayer, Card card) {
        // Check if there are some params null
        if (actualPlayer == null || card == null) {
            throw new IllegalArgumentException();
        }
        try {
            for (Card c : deck) {
                if ((c.getTerrain().equals(card.getTerrain())) && (c.getCost() == card.getCost())) {
                    actualPlayer.removeMoney(card.getCost());
                    deck.remove(c);
                    actualPlayer.addTerrainCard(card);
                    return true;
                }
            }
            return false;
        } catch (NoMoneyException e) {
            LOGGER.log(Level.FINE, CONTEXT, e);
            return false;
        }
    }

    /**
     * 
     * @param currentPlayer
     * @param chosenShepherd
     * @return a list of buyable card for the specific shepherd
     */
    public List<Card> getBuyableCards(Player currentPlayer, int chosenShepherd) {
        // Check if there are some params null
        if (currentPlayer == null) {
            throw new IllegalArgumentException();
        }
        List<Card> buyableCards = new ArrayList<Card>();
        Shepherd actualShepherd = currentPlayer.getShepherd().get(chosenShepherd);
        // Managing sheepsburg is one of the regions
        if (actualShepherd.getCell().getRegion2().getId() == SHEEPSBURG) {
            Terrain terrain1 = actualShepherd.getCell().getRegion1().getTerrain();
            for (Card c : getDeck()) {
                if (terrain1.equals(c.getTerrain())) {
                    buyableCards.add(c);
                    break;
                }
            }
        } else if (actualShepherd.getCell().getRegion1().getId() == SHEEPSBURG) {
            Terrain terrain2 = actualShepherd.getCell().getRegion2().getTerrain();
            for (Card c : getDeck()) {
                if (terrain2.equals(c.getTerrain())) {
                    buyableCards.add(c);
                    break;
                }
            }
        } else { // Normal regions
            Terrain terrain1 = actualShepherd.getCell().getRegion1().getTerrain();
            Terrain terrain2 = actualShepherd.getCell().getRegion2().getTerrain();
            // Check if it's available a card of the terrains of the region near
            // the shepherd.
            // If so add it to the list of buybable cards
            if (!terrain1.equals(terrain2)) {
                for (Card c : getDeck()) {
                    if (terrain1.equals(c.getTerrain())) {
                        buyableCards.add(c);
                        break;
                    }
                }
                for (Card c : getDeck()) {
                    if (terrain2.equals(c.getTerrain())) {
                        buyableCards.add(c);
                        break;
                    }
                }
            } else {
                for (Card c : getDeck()) {
                    if (terrain1.equals(c.getTerrain())) {
                        buyableCards.add(c);
                        break;
                    }
                }

            }
        }
        return buyableCards;
    }

    /**
     * Move the shepherd
     * 
     * @param nextCell
     *            cell to go
     * @param shepherdNumber
     *            Shepherd id
     * @return true if the move has been done correctly
     */
    public boolean moveShepherd(Player currentPlayer, int chosenshepherd, int chosenCell) {
        // Check if there are some params null
        if (currentPlayer == null) {
            throw new IllegalArgumentException();
        }
        Cell nextCell = cell[chosenCell];
        // Check if next chosen cell is free
        if (nextCell.getState() == Cell.FREE) {
            Shepherd currentShepherd = currentPlayer.getShepherd().get(chosenshepherd);
            if (fences == 0) {
                currentShepherd.getCell().setState(Cell.FINAL);
            } else {
                currentShepherd.getCell().setState(Cell.LOCK);
            }
            // Check if is a near cell of actual one
            List<Cell> nearCells = currentShepherd.getCell().getNearCells(cell, region);
            if (!isNear(nextCell, nearCells)) {
                try {
                    currentPlayer.removeMoney(MOVE_SHEPHERD_FAR);
                } catch (NoMoneyException e) {
                    LOGGER.log(Level.FINE, CONTEXT, e);
                    return false;
                }
            }
            currentShepherd.setCell(nextCell);
            currentShepherd.getCell().setState(Cell.OCCUPIED);
            // if there are more normal fences reduce the number
            if (fences != 0) {
                fences--;
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check if the next chosen cell is a near actual one
     * 
     * @param nextCell
     * @param nearCells
     * @return true if is near, false otherwise
     */
    private boolean isNear(Cell nextCell, List<Cell> nearCells) {
        for (Cell c : nearCells) {
            if (c.equals(nextCell)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Move a sheep to a new region
     * 
     * @param region
     * @param sheep
     * @return true if the move has been done correctly
     */
    public boolean moveSheep(Player currentPlayer, int chosenShepherd, Sheep sheep) {
        // Check if there are some params null
        if (currentPlayer == null || sheep == null) {
            throw new IllegalArgumentException();
        }
        Shepherd currentShepherd = currentPlayer.getShepherd().get(chosenShepherd);
        Cell shepherdCell = currentShepherd.getCell();
        // Verifying if the chosen sheep is in a region near the shepherd
        if (sheep.getRegion().equals(shepherdCell.getRegion1())) {
            sheep.getRegion().removeSheep(sheep);
            shepherdCell.getRegion2().addSheep(sheep);
            sheep.setRegion(shepherdCell.getRegion2());
            return true;
        } else if (sheep.getRegion().equals(shepherdCell.getRegion2())) {
            sheep.getRegion().removeSheep(sheep);
            shepherdCell.getRegion1().addSheep(sheep);
            sheep.setRegion(shepherdCell.getRegion1());
            return true;
        } else {
            return false;
        }

    }

    /**
     * Kill a chosen sheep
     * 
     * @param currentPlayer
     * @param chosenShepherd
     * @param sheep
     * @return true if the move has been done correctly
     */
    public boolean killSheep(Player currentPlayer, int chosenShepherd, Sheep sheep) {
        // Check if there are some params null
        if (currentPlayer == null || sheep == null) {
            throw new IllegalArgumentException();
        }
        Shepherd currentShepherd = currentPlayer.getShepherd().get(chosenShepherd);
        Cell shepherdCell = currentShepherd.getCell();
        // Verifying if the chosen sheep is in a region near the shepherd
        if (sheep.getRegion().equals(shepherdCell.getRegion1())) {
            sheep.getRegion().removeSheep(sheep);
            if (sheep.equals(blackSheep)) {
                blackSheep.setKilled(true);
            }
            return true;
        } else if (sheep.getRegion().equals(shepherdCell.getRegion2())) {
            sheep.getRegion().removeSheep(sheep);
            return true;
        } else {
            return false;
        }
    }

    public boolean generateSheep(Player currentPlayer, int chosenShepherd, Sheep sheep) {
        // Check if there are some params null
        if (currentPlayer == null || sheep == null) {
            throw new IllegalArgumentException();
        }
        Shepherd currentShepherd = currentPlayer.getShepherd().get(chosenShepherd);
        Cell shepherdCell = currentShepherd.getCell();
        if (sheep.getRegion().equals(shepherdCell.getRegion1()) && sheep.getRegion().getSheep().size() >= 2) {
            sheep.getRegion().addSheep(sheepFactory.getSheep(sheep.getRegion()));
            return true;
        } else if (sheep.getRegion().equals(shepherdCell.getRegion2()) && sheep.getRegion().getSheep().size() >= 2) {
            sheep.getRegion().addSheep(sheepFactory.getSheep(sheep.getRegion()));
            return true;
        } else {
            return false;
        }

    }

    /**
     * Perform the movement of the black sheep
     */
    public void moveBlackSheep() {
        Region nextRegion;
        // find the new region rolling the dice and checking if the cell is FREE
        nextRegion = findNewRegion(blackSheep.getRegion(), rollDice());
        if (nextRegion != null) {
            blackSheep.getRegion().removeSheep(blackSheep);
            nextRegion.addSheep(blackSheep);
            blackSheep.setRegion(nextRegion);
        }
    }

    /**
     * Perform the movement of the wolf
     */
    public void moveWolf() {
        Region nextRegion;
        int extractNum = rollDice();
        if (checkCell(wolf.getRegion())) {
            nextRegion = findNewRegion(wolf.getRegion(), extractNum);
            if (nextRegion != null) {
                wolf.setRegion(nextRegion);
                eatSheep(nextRegion);
            }

        } else {
            nextRegion = findNewRegionWolf(wolf.getRegion(), extractNum);
            if (nextRegion != null) {
                wolf.setRegion(nextRegion);
                eatSheep(nextRegion);
            }

        }
    }

    /**
     * Perform the eating of the sheep by the wolf
     * 
     * @param nextRegion
     */
    private void eatSheep(Region nextRegion) {
        if (nextRegion.getSheep().size() > 0) {
            Random rand = new Random();
            int i = rand.nextInt(nextRegion.getSheep().size());
            Sheep sheepToRemove = nextRegion.getSheep().get(i);
            if (sheepToRemove.equals(blackSheep)) {
                blackSheep.setKilled(true);
            }
            nextRegion.removeSheep(sheepToRemove);
        }

    }

    /**
     * Check if all cells near a region are locked
     * 
     * @param actualRegion
     * @return false if all cells are locked
     */
    public boolean checkCell(Region actualRegion) {
        for (int cellValue = 1; cellValue <= 6; cellValue++) {
            for (Cell c : cell) {
                if (c.getValue() == cellValue && (c.getRegion1().equals(actualRegion)) && c.getState() == Cell.FREE) {
                    return true;
                } else if (c.getValue() == cellValue && (c.getRegion2().equals(actualRegion)) && c.getState() == Cell.FREE) {
                    return true;
                }
            }
        }
        return false;

    }

    /**
     * Find the new region to move the black sheep
     * 
     * @param actualRegion
     * @param cellValue
     * @return newRegion
     */
    private Region findNewRegion(Region actualRegion, int cellValue) {
        // Check if there are some params null
        if (actualRegion == null) {
            throw new IllegalArgumentException();
        }
        for (Cell c : cell) {
            if (c.getValue() == cellValue && (c.getRegion1().equals(actualRegion)) && c.getState() == Cell.FREE) {
                return c.getRegion2();
            } else if (c.getValue() == cellValue && (c.getRegion2().equals(actualRegion)) && c.getState() == Cell.FREE) {
                return c.getRegion1();
            }
        }
        return null;
    }

    /**
     * Find the new region to move the wolf
     * 
     * @param actualRegion
     * @param cellValue
     * @return newRegion
     */
    private Region findNewRegionWolf(Region actualRegion, int cellValue) {
        // Check if there are some params null
        if (actualRegion == null) {
            throw new IllegalArgumentException();
        }
        for (Cell c : cell) {
            if (c.getValue() == cellValue && (c.getRegion1().equals(actualRegion)) && c.getState() == Cell.LOCK) {
                return c.getRegion2();
            } else if (c.getValue() == cellValue && (c.getRegion2().equals(actualRegion)) && c.getState() == Cell.LOCK) {
                return c.getRegion1();
            }
        }
        return null;
    }

    /**
     * 
     * @return number of shepherd
     */
    public int getSizeShepherd() {
        return shepherd.length;
    }

    /**
     * 
     * @return the extracted number by the dice
     */
    public int rollDice() {
        return dice.roll();
    }

    /**
     * Give initial cards to players
     */
    public void giveCard() {
        // Initialization of decks
        List<Card> initialDeck = deckFactory.deckCreator(STARTER_CARD_FOREACH);
        Collections.shuffle(initialDeck);
        for (int i = 0; i < player.length; i++) {
            Card c = initialDeck.get(i);
            player[i].addTerrainCard(c);
        }
    }

    /**
     * Give initial money to players
     * 
     * @param initialMoney
     */
    private void giveMoney(int initialMoney) {
        for (Player p : player) {
            p.setMoney(initialMoney);
        }
    }

    /**
     * 
     * @return the number of fences available
     */
    public int getFences() {
        return fences;
    }

    /**
     * 
     * @return the list of the player in game
     */
    public Player[] getPlayer() {
        return player;
    }

    /**
     * 
     * @return the list of all cells in the gameboard
     */
    public Cell[] getCell() {
        return cell;
    }

    /**
     * 
     * @return the list of all regions in the gameboard
     */
    public Region[] getRegion() {
        return region;
    }

    /**
     * 
     * @return the blacksheep
     */
    public BlackSheep getBlackSheep() {
        return blackSheep;
    }

    /**
     * Set the start chosen cell of the shepherd
     * 
     * @param player
     * @param shepherdNumber
     * @param cellId
     * @return true if the move has been done correctly
     */
    public boolean setShepherd(Player player, int shepherdNumber, int cellId) {
        // Check if there are some params null
        if (player == null) {
            throw new IllegalArgumentException();
        }
        if (cell[cellId].getState() == Cell.FREE) {
            player.getShepherd().get(shepherdNumber).setCell(cell[cellId]);
            cell[cellId].setState(Cell.OCCUPIED);
            return true;
        }
        return false;
    }

    /**
     * 
     * @return the shepherd of the gameboard
     */
    public Shepherd[] getShepherds() {
        return shepherd;
    }

    /**
     * Calculate final points for each player
     */
    public void calculatePoints() {
        for (int i = 0; i < player.length; i++) {
            int points = 0;
            for (int j = 1; j < region.length; j++) {
                points = points + region[j].getSheep().size() * player[i].getNumberOfCards(region[j].getTerrain());
            }
            points = points + player[i].getMoney();
            player[i].setPoints(points);
        }
    }

    /**
     * @return the deck
     */
    public List<Card> getDeck() {
        return deck;
    }

    /**
     * @return the wolf
     */
    public Wolf getWolf() {
        return wolf;
    }

    /**
     * @param wolf
     *            the wolf to set
     */
    public void setWolf(Wolf wolf) {
        this.wolf = wolf;
    }

}
